package cn.sdxx.qddata.security;


import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.sdxx.qddata.common.R;
import cn.sdxx.qddata.utils.JwtTokenUtils;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/*
 * 【自定义一个Filter】用来拦截所有的请求判断是否携带Token
 * isAccessAllowed()判断是否携带了有效的JwtToken
 * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录，登录成功允许访问，登录失败拒绝访问
 *
 * 原文链接：https://blog.csdn.net/weixin_42516475/article/details/130728492
 * 重写了preHandle、isAccessAllowed、executeLogin、onAccessDenied这4个方法（该例子为简单实现）。
    在JwtFilter中，这4个方法主要的执行顺序是
    preHandle -> isAccessAllowed -> executeLogin -> onAccessDenied


 * */

//@Component
//public class JwtFilter extends AuthenticatingFilter {
public class JwtFilter extends BasicHttpAuthenticationFilter {

//    @Autowired
//    JwtTokenUtils jwtUtils;

    // 用户登录完成后会返回一个jwt给用户
//    @Override
//    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//        // 获取 token
//        HttpServletRequest  req = (HttpServletRequest) request;
//        String jwt = req.getHeader("Authorization");
//        if(StringUtils.isEmpty(jwt)){
//            return null;
//        }
//        //从请求中取出token，创建JwtToken对象
//        return new JwtToken(jwt);
//    }

    /**
     * 执行登录认证
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
                                      ServletResponse response,
                                      Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
//            throw new AuthenticationException("你的Token已失效，请重新登录");
            /* *
             * 这个问题纠结了好久
             *   原生的shiro验证失败会进入全局异常 但是 和JWT结合以后却不进入了 之前一直想不通
             *   原因是 JWT直接在过滤器里验证 验证成功与否 都是直接返回到过滤器中 成功才进入controller
             *   失败直接返回进入springboot自定义异常处理页面，所以在这里直接返回错误JSON数据
             */
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            JSONObject resultMap = new JSONObject();
            resultMap.put("code","401");
            resultMap.put("msg","token无效，请重新获取。");
            resultMap.put("data","null");

            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            try {
                PrintWriter out = httpServletResponse.getWriter();
                out.append(resultMap.toString());
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
        return false;
    }


//    @Override
//    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
//            throws Exception {
//        throw new ExpiredCredentialsException("token已失效，请重新登录！");
        // 执行登录
        // token不为空且状态正常,我们将进行登录处理
        // 这个executeLogin会拿到我们的token信息，Subject.login(token),去交给我们自定义的Realm进行登录
        // 最终会走到我们自定义Realm的goGetAuthenticationInfo(),
//        return executeLogin(request, response);
//        return false;  // true请求继续，false 返回失败结果
//
//    }

    /**
     * 执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        //  判断是否已过期
//        Claims claim = jwtUtils.parseToken(token);
//        if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
//            throw new ExpiredCredentialsException("token已失效，请重新登录！");
//        }
        // 继续登录
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        // 这里真正实现shiro的登录，getSubject(request, response)直接SecurityUtils.getSubject()也一样
        // 使用自定义的JWTToken而不是默认的UsernamePasswordToken
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            R r = R.fail(throwable.getMessage());
            String json = JSONUtil.toJsonStr(r);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {

        }
        return false;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

        // 跨域时会首先发送一个OPTIONS请求，这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}
